/*
 * JSF Toolkit Component Framework
 * Copyright (C) 2007 Noah Sloan <iamnoah A-T gmail D0T com>
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 */
package com.jsftoolkit.utils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;

import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

import org.apache.shale.remoting.Mechanism;
import org.apache.shale.remoting.XhtmlHelper;
import org.apache.shale.remoting.faces.ResponseFactory;

import com.jsftoolkit.base.ResourceConstants;

/**
 * Translates expressions of the form ${variableName|default value}.
 * 
 * Resource references get translated to proper remoting URLs.
 * <p>
 * e.g. 'url(${foo|resource:/com/yourCompany/foo.png})' gets translated to
 * something like 'url(/AppName/faces/static/com/yourCompany/foo.png)' (assuming
 * foo is undefined).
 * <p>
 * The syntax ${resource:/foo/bar/baz.png} is allowed, but not recommended.
 * <p>
 * An instance of this class is registered as a managed bean under the name
 * {@value ResourceConstants#SKINNING_FILTER}.
 * 
 * 
 * @author noah
 * 
 */
public class ResourceFilterImpl implements ResourceFilter {

	private static final String RESOURCE_PREFIX = "resource:";

	private static final XhtmlHelper HELPER = new XhtmlHelper();

	private String prefix = "${";

	private String suffix = "}";

	private int bufferSize = 65536;

	private Map<String, String> variables = new HashMap<String, String>();

	/**
	 * Stateless factory for {@link ResponseWriter} instances.
	 */
	private static final ResponseFactory RESPONSE_FACTORY = new ResponseFactory();

	/**
	 * The resource is loaded and copied to the response, translating
	 * occurrences of a pattern enclosed in {@link #prefix} and {@link #suffix}
	 * with proper shale remoting URLs to the referenced resource.
	 * 
	 * @see com.jsftoolkit.utils.ResourceFilter#writeFilteredResource()
	 */
	public void writeFilteredResource() throws IOException {
		FacesContext context = FacesContext.getCurrentInstance();
		Map<String, String> requestMap = context.getExternalContext()
				.getRequestParameterMap();

		// get the parameters
		String resourceId = (String) requestMap.get(RESOURCE_KEY);
		String contentType = Utils.toString(requestMap.get(CONTENT_TYPE_KEY),
				"text/plain");
		String charsetName = Utils.toString(requestMap.get(CHARSET_KEY),
				"UTF-8");
		Charset charset = Utils.getCharset(charsetName);

		// create a response writer
		ResponseWriter out = RESPONSE_FACTORY.getResponseWriter(context,
				contentType);

		if (resourceId != null) {
			// load and filter the resource
			InputStream stream = getClass().getResourceAsStream(resourceId);
			if (stream != null) {
				processResource(new InputStreamReader(stream, charset), out,
						context);
			}
		}

		out.close();
	}

	/**
	 * This method pulls out the stream processing for 2 purposes. 1. So
	 * subclasses can implement only the stream processing aspect. 2. So it can
	 * be invoked by non-subclasses wishing to decorate.
	 * 
	 * @param input
	 *            not null - the resource contents
	 * @param output
	 *            where to write the response
	 * @param context
	 *            the current {@link FacesContext}
	 * @throws IOException
	 */
	public void processResource(InputStreamReader input, ResponseWriter output,
			FacesContext context) throws IOException {
		// decorate the stream appropriately
		IsFinishedReader in = new IsFinishedReader(new BufferedReader(input));
		DelimitedReader reader;

		// temp buffer for copying to the response writer
		char[] buffer = new char[bufferSize];

		while (!in.isFinished()) {
			// copy up to an expression, discarding the prefix
			reader = new DelimitedReader(in, prefix);
			Utils.writeAll(reader, output, buffer);

			if (!in.isFinished()) {
				// read the whole expression
				String expr = Utils.toString(new DelimitedReader(reader
						.getReader(), suffix));
				String[] split = expr.split("\\|", 2);

				// determine the value
				String def = Utils.get(split, 1);
				String value = Utils.getValue(variables.get(split[0]), def);

				// if the value starts with resource:
				boolean isResource = value != null
						&& value.startsWith(RESOURCE_PREFIX);
				// or the expression started with resource:
				if (!isResource && split[0].startsWith(RESOURCE_PREFIX)) {
					// XXX warn about hard coded resource?
					isResource = true;
					value = split[0];
				}

				if (isResource) {
					// map the resource to a remoting URL
					String url = HELPER.mapResourceId(context,
							Mechanism.CLASS_RESOURCE, value
									.substring(RESOURCE_PREFIX.length()));
					output.write(url);
				} else if (value == null) {
					// leave it alone if it doesn't match anything
					output.write(prefix);
					output.write(expr);
					output.write(suffix);
				} else {
					// otherwise, write the value
					output.write(value);
				}
			}
		}
	}

	/**
	 * 
	 * @return the prefix of resource references e.g. '${'
	 */
	public String getPrefix() {
		return prefix;
	}

	public void setPrefix(String prefix) {
		this.prefix = prefix;
	}

	/**
	 * 
	 * @return the suffix of resource references, e.g. '}'
	 */
	public String getSuffix() {
		return suffix;
	}

	public void setSuffix(String suffix) {
		this.suffix = suffix;
	}

	/**
	 * 
	 * @return the size of the buffer to use while reading the resource.
	 */
	public int getBufferSize() {
		return bufferSize;
	}

	public void setBufferSize(int bufferSize) {
		this.bufferSize = bufferSize;
	}

	public Map<String, String> getVariables() {
		return variables;
	}

	public void setVariables(Map<String, String> variables) {
		this.variables = variables;
	}

}
